/*
 * Created on 2008-1-10
 *
 * TODO 
 */
package jacky.lanlan.song.extension.struts.util;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.apache.log4j.Logger;

/**
 * 关于包操作的实用类。
 * @author Jacky.Song
 */
public abstract class PackageUtils {
	private final static Logger logger = Logger.getLogger(PackageUtils.class);
	
	/**
	 * 列出包下面的类。
	 * @param packageName 要寻找的包的包名
	 * @return 这个包下面的所有类
	 */
	public static List<Class<?>> findClassInPackage(String packageName){
		File baseDir=getSearchBaseDir(packageName);
		
		Collection<File> files=getAllClassFiles(baseDir);
		List<Class<?>> classes=new ArrayList<Class<?>>();
		try {
			StringBuilder path=new StringBuilder();
			for(File file:files) {
				path.append(file.getPath());
				Class<?> c=Class.forName(convertToFullClassName(path,packageName));
				classes.add(c);
				path.delete(0, path.length());//清空
			}
		}
		catch (Exception e) {
			logger.error("查找包"+packageName+"下面的类时出错 ",e);
			throw new RuntimeException("查找包"+packageName+"下面的类时出错 "+e);
		}
		files=null;
		return classes;
	}
	private static Collection<File> getAllClassFiles(File dir) {
		List<File> files=new ArrayList<File> ();
		FileUtils.listFiles(dir, files,".class");//委托FileUtils得到dir下所有的class文件
		return files;
	}
	private static String convertToFullClassName(StringBuilder filePath,String basePackage) {
		return filePath.delete(0, filePath.indexOf(basePackage.replace('.',File.separatorChar))).//截去绝对路径前缀
						delete(filePath.length()-6, filePath.length()).//去掉文件后缀名(".class".length()是6)
						toString().replace(File.separatorChar, '.');//最后把路径分割符转换为'.'，变成全限定类名
	}

	/**
	 * 寻找最匹配指定字符串的包名。
	 * <p style="color:green">
	 * 比如，有包名<code>"aaa.bbb","aaa.bbb.ccc","aaa.ccc"</code>，
	 * 指定字符串为<code>"aaa.bbb.ccc.ddd.SomeClass"</code>，则该方法
	 * 返回最匹配包名：<code>aaa.bbb.ccc</code>
	 * @param packageNames 待搜索的包名数组
	 * @param key 指定的字符串
	 * @return packageNames 里面最匹配 key 的包名
	 */
	public static String findMostMatchPackage(String [] packageNames,String key){
		//算法：用包名“切”key，看哪个切后，key 剩下最少，就是最匹配的
		//注意 leftCharNum和packageNames 的元素一定要一一对应
		int [] leftCharNumArray=new int[packageNames.length];
		for (int i=0;i<packageNames.length;i++) {
			leftCharNumArray[i]=key.replace(packageNames[i], "").length();//切包名
		}
		//找最小那个
		int [] copy=new int[leftCharNumArray.length];
		System.arraycopy(leftCharNumArray, 0, copy, 0, leftCharNumArray.length);
		Arrays.sort(copy);//排序后，copy[0]里面就是最小的那个
		
		//定位最匹配的那个Package名
		String realBasePack=null;
		for (int i=0;i<leftCharNumArray.length;i++) {
			if(leftCharNumArray[i]==copy[0]) {
				realBasePack=packageNames[i];//找到“切”后所剩最少的那个，必是最匹配的那个
				break;
			}
		}
		return realBasePack;
	}
	
	/**
	 * 列出指定包下面所有的子包。
	 * @param basePack 寻找的起点包
	 * @return 指定包的所有子包名称列表
	 */
	public static String [] listSubPackage(String basePack) {
		File baseDir=getSearchBaseDir(basePack);
		//得到basePack下的子目录
		List<File> files=new ArrayList<File>();
		FileUtils.listDirs(baseDir, files);
		
		String [] packList=new String[files.size()];
		
		//得到包的基本路径(对于EE应用来说，就是WEN-INF/classes目录)
		String basePkgFilePath=PackageUtils.class.getClassLoader().getResource("").toString();
		//全目录前缀
		String prifixPath=basePkgFilePath.substring(6).replace("%20", " ").replace('/', '\\');
		for (int i = 0; i < packList.length; i++) {
			packList[i]=files.get(i).getPath().replace(prifixPath, "").replace('\\', '.');// 通过转换路径得到包名
		}
		return packList;
	}
	
	private static File getSearchBaseDir(String basePack) {
		Assert.isTrue(matchesSpec(basePack),basePack+" 包名格式错误");
		//得到包的基本路径(对于EE应用来说，就是WEN-INF/classes目录)
		String basePkgFilePath=PackageUtils.class.getClassLoader().getResource("").toString();
		//得到开始搜索路径(基本路径+用户指定basePackage路径)
		String startSearchPath=basePkgFilePath+basePack.replace('.', '/');
		File baseDir=null;
		try {
			baseDir = new File(new URI(startSearchPath));
		}
		catch (URISyntaxException e) {
			logger.error("搜索包路径 ["+startSearchPath+"] 时出错",e);
			throw new RuntimeException("搜索包路径 ["+startSearchPath+"] 时出错"+e);
		}
		return baseDir;
	}
	
	/**
	 * 操作指定包下面的类。
	 * @param packName 指定包的包名
	 * @param classHandler 类处理器
	 */
	public static void doWithClassInPackage(String packName,Handler<Class<?>> classHandler){
		for(Class<?> clazz : findClassInPackage(packName)) {
			classHandler.doWith(clazz);
		}
	}

	/**
	 * 从指定包中过滤出需要的类。
	 * @param packName 指定包的包名
	 * @param filter 过滤器，符合该过滤器条件的类将被过滤出来
	 * @return 符合filter条件的类列表
	 */
	public static Collection<Class<?>> findClassInPackage(String packName,Judgment<Class<?>> filter) {
		List<Class<?>> orig = findClassInPackage(packName);
		List<Class<?>> list = new ArrayList<Class<?>> (orig.size());
		for(Class<?> clazz : orig) {
			if(filter.canPass(clazz)) {
				list.add(clazz);
			}
		}
		return list;
	}
	
	/**
	 * 测试给定包名是否符合Java规范。
	 * @param packName 要测试的包名
	 * @return true如果符合，false如果不符合
	 */
	public static boolean matchesSpec(String packName) {
		return packName.matches(
				"[[^\\d] && \\w]" + //字母或'_'开头，只能有1个
				"[\\w]*" + //后面跟>=0个数字、字母或'_'
				"(" + //分组
				"\\." + //分组必须以'.'开头
				"[[^\\d] && \\w]" + //字母或'_'开头，只能有1个
				"[\\w]*" + //后面跟>=0个数字、字母或'_'
				")*");//分组可以有>=0个
		// 一起写就是[[^\\d] && \\w][\\w]*(\\.[[^\\d] && \\w][\\w]*)*
	}
	
}
